package com.mileoresko.hierarchism.hsms;

import com.mileoresko.hierarchism.HSMEvent;
import com.mileoresko.hierarchism.HSMInternalTransitionAdapter;
import com.mileoresko.hierarchism.HSMState;
import com.mileoresko.hierarchism.HSMTerminalState;
import com.mileoresko.hierarchism.HierarchicalStateMachine;
import com.mileoresko.hierarchism.exceptions.HSMCommonTransitionDefinitionException;
import com.mileoresko.hierarchism.exceptions.HSMDuplicateTransitionException;
import com.mileoresko.hierarchism.exceptions.HSMInternalTransitionDefinitionException;
import com.mileoresko.hierarchism.exceptions.HSMPseudoChoiceDefinitionException;
import com.mileoresko.hierarchism.system.SchedulingServices;

public abstract class LoopHSM extends HierarchicalStateMachine {

    private Object pendingEvent;
    private LoopHsmConfiguration configuration;
    private Object loopArgument;

    private final class LoopState extends HSMState {

        protected LoopState(final HierarchicalStateMachine owningStateMachine, final HSMEvent triggerEvent) {
            super(owningStateMachine, "LoopState");
        }

        protected void defineNestedHsms() {
            // none predicted
        }

        protected void entryAction(final Object arg) {
            // called only on HSM start -> kick-start scheduler here
            pendingEvent = scheduler.scheduleRepetitiveEvent(LoopHSM.this, triggerEvent, arg,
                    configuration.getLoopIntervalMs(), configuration.getLoopIntervalMs());
        }

        protected void exitAction(final Object arg) {
            // called only on HSM exit -> abort scheduler here
            scheduler.cancelPendingEvents(pendingEvent);
        }
    }

    private final class HaltState extends HSMTerminalState {

        protected HaltState(HierarchicalStateMachine owningStateMachine) {
            super(owningStateMachine, "LoopEnded");
        }

        protected void entryAction(Object arg) {
            // does nothing
        }
    }

    private final LoopState loopState;
    private final HaltState haltState;
    private final HSMEvent triggerEvent;
    private final HSMEvent haltEvent;
    private final HSMEvent reconfigEvent;
    private final SchedulingServices scheduler;
    private final LoopHsmConfiguration defaultConfiguration;

    protected LoopHSM(final String label, final HSMEvent triggerEvent, final HSMEvent haltEvent,
            final HSMEvent reconfigEvent, final SchedulingServices scheduler,
            final LoopHsmConfiguration defaultConfiguration) {
        super(label);
        this.triggerEvent = triggerEvent;
        this.haltEvent = haltEvent;
        this.reconfigEvent = reconfigEvent;
        this.scheduler = scheduler;
        loopState = new LoopState(this, triggerEvent);
        haltState = new HaltState(this);
        this.defaultConfiguration = defaultConfiguration;
    }

    protected void defineTransitions() throws HSMDuplicateTransitionException,
            HSMInternalTransitionDefinitionException, HSMPseudoChoiceDefinitionException,
            HSMCommonTransitionDefinitionException {
        addInternalTransition(loopState, triggerEvent, new HSMInternalTransitionAdapter() {
            public void transitionAction(final Object arg) {
                onLoopEvent(arg);
            }
        });
        addInternalTransition(loopState, reconfigEvent, new HSMInternalTransitionAdapter() {
            public void transitionAction(Object arg) {
                scheduler.cancelPendingEvents(pendingEvent);
                LoopHsmConfiguration newConfiguration = collectConfigurationOnReconfig(arg);
                if (newConfiguration != null) {
                    configuration = newConfiguration;
                }
                Object loopArg = collectLoopArgumentOnReconfig(arg);
                if (loopArg != null) {
                    loopArgument = loopArg;
                    onLoopArgumentChange(loopArgument);
                }
                scheduler.scheduleRepetitiveEvent(LoopHSM.this, triggerEvent, loopArgument,
                        configuration.getLoopIntervalMs(), configuration.getLoopIntervalMs());
            }
        });
        addCommonTransition(loopState, haltEvent, haltState, null);
    }

    protected HSMState getInitialState() {
        return loopState;
    }

    protected void onStart(Object arg) {
        super.onStart(arg);
        configuration = collectConfigurationOnStart(arg);
        if (configuration == null) {
            configuration = defaultConfiguration;
        }
        loopArgument = collectLoopArgumentOnStart(arg);
        if (configuration.doTriggerImmediately()) {
            onLoopEvent(loopArgument);
        }
        pendingEvent = scheduler.scheduleRepetitiveEvent(LoopHSM.this, triggerEvent, loopArgument,
                configuration.getLoopIntervalMs(), configuration.getLoopIntervalMs());
    }

    protected abstract void onLoopEvent(Object loopArg);

    protected abstract void onLoopArgumentChange(Object loopArg);

    protected abstract LoopHsmConfiguration collectConfigurationOnStart(Object eventArg);

    protected abstract Object collectLoopArgumentOnStart(Object eventArg);

    protected abstract LoopHsmConfiguration collectConfigurationOnReconfig(Object eventArg);

    protected abstract Object collectLoopArgumentOnReconfig(Object eventArg);
}
